package hhf.flex.soundapi.ui {
	import flash.display.BitmapData;
	import flash.display.GradientType;
	import flash.display.Graphics;
	import flash.display.GraphicsPathCommand;
	import flash.display.GraphicsPathWinding;
	import flash.events.Event;
	import flash.media.Sound;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import hhf.flex.soundapi.core.WavSound;
	
	import mx.core.UIComponent;
	import mx.events.StyleEvent;
	
	/**
	 * Classe WaveForms
	 * Dessine l'onde de form du sound passé en argument
	 * accepte les sounds mp3 ou lew WavSounds
	 * 
	 * 
	 * Sample : couple de mesure pour un echantillon 
	 */
	[Style(name="rightColor", type="uint", format="Color", inherit="yes")]
	[Style(name="leftColor", type="uint", format="Color", inherit="yes")]
	[Style(name="axeColor", type="uint", format="Color", inherit="yes")]
	[Style(name="direction", type="String", enumeration="ltr,rtl,overlay", inherit="yes")]
	public class WaveForms extends UIComponent {
		
		public function WaveForms() {
			this.setStyle("leftColor", 0x6600CC); // couleur du canal gauche
			this.setStyle("rightColor", 0xCC0066); // couleur du canal droit
			this.setStyle("axeColor", 0xcccc00); // couleur  de l'axe
		}

		
		// debug mode
		private var debug:Boolean = false;
		private var t0:Number = 0;

		/**
		 * Active ou desactive le dessin
		 */
		public var draw:Boolean = true;

		override public function setStyle(styleProp:String, newValue:*):void {
			if("direction" == styleProp) {
				if(newValue == "rtl") {
					yLeft = quart;	
					yRight = quart*3;
				} else if(newValue == "overlay") { // overlay
					quart = 2 * quart;
					yLeft = quart;	
					yRight = quart;
				} else {  // left To Right par defaut
					yLeft = quart*3;
					yRight = quart;	
				}
			}
			if(newValue != getStyle(styleProp)) {
				isInit = false;
				isDraw = false;
			}
			super.setStyle(styleProp, newValue);
			drawAxe();
		}

		public var samplerate:int = 44100; // échantillonage

		
		public var skipLRSample:int; // nombre de sample ignorés  à chaque iteration, calculé lors du chargement du son
		private var skipCoeff:int = 0; // coefficient multiplicateur pour les samples ignorés
		private var extractionLengthLRSample:int = 44100; // nombre de sample à extraire à chaque itération
		
		/**
		 * offset d'extraction, incrémenté à chaque ecriture d'un sample.
		 * Le nombre de couple de samples deja lus
		 */
		private var offsetLRSampleExtraction:int = 0;
		/**
		 * Nombre de bytes par sample left et right
		 * si extract : float alors 4x2, 
		 * si extractPcm : int depend du nombre de bit du format : 8bit, 16bit, 32bit => (2,4,8)x2 bytes 
		 */
		private var bytesByLRSample:int = 8;
		
		/**
		 * La taille d'un sample à l'ecran
		 */
		private var samplewidth:Number; 
		/**
		 * La derniere position de X
		 * Utile car on dessine alternativement la droite puis la gauche
		 */
		private var previousX:int = 0;
		private var samplePLeft:Number;
		private var samplePRight:Number;
		private var sampleNLeft:Number;
		private var sampleNRight:Number;
		private var yLeft:Number;
		private var yRight:Number;
		private var quart:Number; // Base de calcul des Centres verticaux : quart ou 3*quart
		private var isInit:Boolean = false;	
		private var isDraw:Boolean = false;	
		private var _sound:Sound;
		[Bindable]
		public function get sound():Sound {
			return this._sound;
		}
		public function set sound(value:Sound):void {
			isInit = false;
			isDraw = false;
			quart = this.height/4;
			removeEventListeners();
			if(!value) return;
			bytesByLRSample= 8;
			addEventListener(Event.ENTER_FRAME, drawWavformsOnRender);
			if(value is WavSound) {
				log("Chargement d'un wav");
			} else {
				log("Chargement d'un mp3");
			}
			this._sound = value;
			setStyle("direction", getStyle("direction"));
		}
		
		/**
		 * Supprime tous les eventlisteners 
		 */
		private function removeEventListeners():void {
			removeEventListener(Event.ENTER_FRAME, drawWavformsOnRender);
		}
		
		/**
		 * Le mp3 est chargé
		 */
		private function init():void {
			if(sound && sound.bytesTotal && (sound.bytesLoaded == sound.bytesTotal)) {
				t0 = new Date().time;
				this.graphics.clear();
				offsetLRSampleExtraction = 0;
				previousX = -1;
				samplePLeft = 0;
				samplePRight = 0;
				sampleNLeft = 0;
				sampleNRight = 0;
				var sec:Number = sound.length/1000;
				if(this.sound is WavSound) {
					bytesByLRSample = ((this.sound as WavSound).fmtChunk.bitsPerSample*(this.sound as WavSound).fmtChunk.numChannels)/8;
					samplerate = (this.sound as WavSound).sampleRate;
				}
				extractionLengthLRSample = Math.ceil(sec/this.width)*samplerate;
				samplewidth = this.width /(sec * samplerate);
				skipLRSample = Math.pow(2, int((Math.log(sec)/Math.log(2)))+1); // calcul du nombre de float ignorés à chaque itération
				isInit = true;
			}
		}

		private var passage:int = 0;
		
		/**
		 * Dessine les waveforms pendant le render dela scene
		 */
		private function drawWavformsOnRender(e:Event=null):void {
			removeEventListeners();
			if(!isInit) {
				init();
				passage = 0;
			}
			if(isInit && !isDraw) {
				var soundBytes:ByteArray = new ByteArray();
				//  read:int = sound.extract(soundBytes, nbample, skipsample); pour 1 sec en 44100 : nbample(l/r) = 1*44100, skipsample = nb couple de sample
				var read:int = extractSound(soundBytes, extractionLengthLRSample, offsetLRSampleExtraction);
				if(read==-1) {
					isDraw= true;
					log("temps de dessin de l'onde de forme : "+(new Date().time-t0)+"ms");
					removeEventListeners();
					return;
				}
				soundBytes.position = 0;
				var drawCoordsL:Vector.<Number> = new Vector.<Number>(); 
				var drawCommandsL:Vector.<int> = new Vector.<int>(); 
				var drawCoordsR:Vector.<Number> = new Vector.<Number>(); 
				var drawCommandsR:Vector.<int> = new Vector.<int>();
				if(previousX==-1) {
					drawCoordsL.push(0, yLeft);
					drawCoordsR.push(0, yRight);
					drawCommandsL.push(GraphicsPathCommand.MOVE_TO);
					drawCommandsR.push(GraphicsPathCommand.MOVE_TO);
				}
				while(soundBytes.bytesAvailable>=bytesByLRSample) { // on lit tous les bytes disponibles, mais on veut minimum de quoi lire 2 samples
					var currentX:int = offsetLRSampleExtraction*samplewidth;
					if(currentX!=previousX && previousX!=-1) { // on a changé de X, donc on dessine les points max sur l'ancien X
						drawCoordsL.push(previousX, yLeft - sampleNLeft*quart);
						drawCoordsR.push(previousX, yRight - sampleNRight*quart);
						drawCommandsL.push(GraphicsPathCommand.MOVE_TO);
						drawCommandsR.push(GraphicsPathCommand.MOVE_TO);
						
						drawCoordsL.push(previousX, yLeft - samplePLeft*quart);
						drawCoordsR.push(previousX, yRight - samplePRight*quart);
						drawCommandsL.push(GraphicsPathCommand.LINE_TO);
						drawCommandsR.push(GraphicsPathCommand.LINE_TO);
						samplePLeft = 0; 
						samplePRight = 0;
						sampleNLeft = 0; 
						sampleNRight = 0;
					}
					var sample:Number = soundBytes.readFloat(); // lit 4 octets car float
					samplePLeft = Math.max(sample, samplePLeft);
					sampleNLeft = Math.min(sample, sampleNLeft);
					sample = soundBytes.readFloat(); 
					samplePRight = Math.max(sample, samplePRight);
					sampleNRight = Math.min(sample, sampleNRight);
					offsetLRSampleExtraction++;
					previousX = currentX;
				}
				if(draw) {
					var g:Graphics = this.graphics;
					g.lineStyle(1, getStyle("leftColor"));
					g.drawPath(drawCommandsL, drawCoordsL);
					g.lineStyle(1, getStyle("rightColor"));
					g.drawPath(drawCommandsR, drawCoordsR);
				}
				offsetLRSampleExtraction += skipLRSample*skipCoeff;
			}
			addEventListener(Event.ENTER_FRAME, drawWavformsOnRender);
		}

		/**
		 * Extrait le son
		 * Corrige le nombre de donnes lu pour les mp3, si 0 => -1
		 */
		private function extractSound(target:ByteArray, length:Number, offset:Number):Number {
			var read:int = sound.extract(target, length, offset);
			if(!this.sound is WavSound) { // les mp3 retourne 0 quand c'est finit, les wav -1
				read = (read)?read:-1;
			}
			return read;
		}

		/**
		 * Dessine l'axe
		 */
		private function drawAxe():void {
			var verticalCenter:Number = this.height/2; // Centre vertical
			var w:Number = this.width; // largeur de la zone de dessin
			this.graphics.lineStyle(1, getStyle("axeColor"));
			this.graphics.moveTo(0, verticalCenter);
			this.graphics.lineTo(w, verticalCenter); // Dessine un trait horizontal noir
		}

		/**
		 * logging
		 */ 
		private function log(msg:String):void {
			if(debug) {
				trace(msg);			
			}
		}
	}
}